/**
 * Created with JackHou
 * Date: 2018/11/19
 * Time: 11:33
 * Description: WebSocket工具类
 * 实现心跳检测的思路是：每隔一段固定的时间，向服务器端发送一个ping数据，
 * 如果在正常的情况下，服务器会返回一个pong给客户端，如果客户端通过
 * onmessage事件能监听到的话，说明请求正常，这里我们使用了一个定时器，每隔n秒的情况下，
 * 如果是网络断开的情况下，在指定的时间内服务器端并没有返回心跳响应消息，
 * 因此服务器端断开了，因此这个时候我们使用ws.close关闭连接，
 * 可以通过 onclose事件监听到。因此在onclose事件内，我们可以调用reconnect事件进行重连操作。
 */
import {URL} from "../Api";
import protoRoot from "@/net/proto/proto";
import DateUtils from "../../utils/DateUtils";
import StringUtils from "../../utils/StringUtils";
import EmitterUtils from "../../utils/EmitterUtils";
import NumUtils from "../../utils/NumUtils";
import {Command2EnStr, EmitterType as EventType, WsConnectType, WsState} from "@/const/Const";
import WsMsgBean from "@/bean/WsMsgBean";
import ProtoParseUtils from "@/net/proto/ProtoParseUtil";

let Command = protoRoot.lookup('Command')
let CommandType = protoRoot.lookup("CommandType")
let Code = protoRoot.lookup('Code')
let WSMessage = protoRoot.lookup('WSMessage')
let PublicMsg = protoRoot.lookup('Public')
let wsSocket, parseUtil, timer, lockReconnect = false;

//心跳检测
let heartCheck = {
    timeout: 5000,//心跳时间
    timeoutObj: null,
    serverTimeoutObj: null,
    start: function () {
        let self = this;
        this.timeoutObj && clearTimeout(this.timeoutObj);
        this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
        this.timeoutObj = setTimeout(function () {
            WsUtils.sendEvent(
                StringUtils.generateWsTid(),
                Command.values.Heartbeat,
                PublicMsg.encode({content: DateUtils.getNowTime()}).finish());
            self.serverTimeoutObj = setTimeout(function () {
                console.log('head beat timeout close websocket！');
                WsUtils.getWsInstance().closeWeSocket();
            }, self.timeout);
        }, this.timeout)
    },
};

export default class WsUtils {
    constructor() {
        parseUtil = ProtoParseUtils.getInstance()
    }

    /**
     * 创建单例Ws
     * @returns {WsUtils}
     */
    static getWsInstance = () => {
        if (!this.wsInstance) {
            this.wsInstance = new WsUtils();
        }
        return this.wsInstance;
    };

    /**
     * 处理ws的response
     * @param cmd
     * @param response
     */
    wsResponseHandler = (cmd, response) => {
        if (cmd === Command.values.Heartbeat) {
            console.log('Receive ws Heartbeat')
        }
        switch (response.code) {
            case Code.values.Success:
                //非心跳事件再发射出去
                if (cmd !== Command.values.Heartbeat) {
                    EmitterUtils.getInstanceEmitter().emit(EventType.WsEvent, cmd, response);//发射事件
                }
                break;

            case Code.values.NoParameter:
                console.error('无参数');
                break;
            case Code.values.InvalidParameter:
                console.error('非法参数');
                break;
            case Code.values.HasExisted:
                console.error(response.msg + ' 已存在');
                break;
            case Code.values.DBError:
                console.error('数据库错误');
                break;
            case Code.values.NoRecord:
                console.log('无记录');
                break;
            case Code.values.ExecuteError:
                console.error('执行错误, ' + response.msg);
                break;
            case Code.values.TokenInvalid:
                console.error('非法Token');
                break;
            case Code.values.NoPermission:
                console.error('无权限操作');
                break;
            case Code.values.GatewayOffline:
                console.error('网关离线');
                break;
            default:
                break;
        }
    };

    /**
     * 创建WebSocket client
     */
    createWebSocket = () => {
        if (wsSocket) {
            console.log('Websocket has created');
            return;
        }
        let clientId = StringUtils.generateWsId();
        /*let edge = SessionStorageUtils.getObj(KeyEnStr.SelectedEdge);
        if (!edge) {
            message.warning('请选择一个edge进行连接!');
            return undefined
        }*/
        try {
            let url = URL.DefaultWsHost.format(clientId, StringUtils.generateWsTid(1), WsConnectType.Client);
            console.log('wsUrl', url);
            wsSocket = new WebSocket(url);
            wsSocket.onopen = () => {
                console.log('WebSocket is open now！');
                heartCheck.start();
            };

            wsSocket.onmessage = (event) => {
                heartCheck.start();
                parseUtil.parseWsMessage(event).then(result => {
                    this.wsResponseHandler(result.cmd, result.resp);
                });//交由parse工具去处理
            };

            wsSocket.onclose = (event) => {
                console.log("WebSocket is closed now！Reason：", event);
                this.reConnect(clientId);
            };

            wsSocket.onerror = (event) => {
                console.log("WebSocket error observed：", event.message);
                this.reConnect(clientId);
            };
        } catch (e) {
            console.log('Create websocket err', e);
            this.reConnect(clientId);
        }
    };

    /**
     * 重连
     */
    reConnect = () => {
        console.log('WebSocket reConnect lockReconnect=', lockReconnect);
        if (lockReconnect) {
            return
        }
        lockReconnect = true;
        //没有连接上会一直重连，设置延时避免请求过多
        timer && clearTimeout(timer);
        timer = setTimeout(() => {
            console.log('createWebSocket in reConnect');
            this.createWebSocket();
            lockReconnect = false;
        }, 6000);
    };

    /**
     * close WebSocket client
     * 注意：服务端需要回复ws消息否则客户端会主动关闭websocket
     */
    closeWeSocket = () => {
        console.log('Start close WebSocket！');
        if (wsSocket !== undefined) {
            wsSocket.close();
            wsSocket = undefined;
            console.log('WebSocket closed！');
        }
    };

    /**
     * 重连使能,避免手动关闭的socket仍然重连
     * 注:主动创建的时候记得从新使能
     * @param disable
     */
    disableReConnect = (disable) => {
        lockReconnect = disable;
    };

    /**
     * 发送消息
     * @param tid
     * @param cmd
     * @param data  结构体
     */
    static sendEvent = (tid, cmd, data) => {
        WsUtils.getWsInstance().sendWsEvent(tid, cmd, data);
    };


    /**
     * 发送websocket数据
     * @param tid
     * @param cmd
     * @param data
     * @returns {Promise<void>}
     */
    sendWsEvent = (tid, cmd, data) => {
        this.sendData(new WsMsgBean(
            StringUtils.generateWsId(),
            tid,
            cmd,
            data,//data 结构体
            CommandType.values.Req,
            String(NumUtils.generateTag()),
        ))
    };


    /**
     * 使用websocket发送数据
     * @param msg Ws消息实体
     */
    sendData = async (msg) => {
        if (WSMessage === undefined) {
            console.error('WSMsg is undefined');
            return
        }

        let any, wsMsg;
        if (msg.data) {
            any = parseUtil.obj2Any(msg.cmd, msg.data);
            wsMsg = {
                id: msg.id,
                tid: msg.tid,
                cmd: msg.cmd,
                cmdType: msg.cmdType == null ? CommandType.values.Req : msg.cmdType,
                tag: msg.tag,
                data: any,
            };
        } else {
            wsMsg = {
                id: msg.id,
                tid: msg.tid,
                cmd: msg.cmd,
                cmdType: msg.cmdType == null ? CommandType.Req : msg.cmdType,
                tag: msg.tag,
            };
        }

        console.log('WSMessage before encode ---->', wsMsg);
        let buffer = WSMessage.encode(wsMsg).finish();

        /*~~~~~~~~~~~~~~~~~~~~~~~~~~~测试代码正式环境去掉~~~~~~~~~~~~~~~~~~~~~~~~*/
        let decodeWsMsg = WSMessage.decode(buffer);
        console.log('test code 111----->', decodeWsMsg.data);
        if (decodeWsMsg.cmd === Command.values.Heartbeat) {
            console.log('test code 222----->', PublicMsg.decode(decodeWsMsg.data.value));
        }
        console.log('decodeWsMsg', decodeWsMsg)
        /*~~~~~~~~~~~~~~~~~~~~~~~~~~~测试代码正式环境去掉~~~~~~~~~~~~~~~~~~~~~~~~*/

        if (wsSocket === undefined) {
            console.log('WebSocket send data error，wsSocket is undefined');
        } else if (wsSocket.readyState !== WsState.Open) {
            console.log('WebSocket send data err，wsSocket state is', wsSocket.readyState);
            this.closeWeSocket();
            console.log('createWebSocket in sendData');
            this.createWebSocket();
        } else {
            wsSocket.send(buffer);//send Protobuf数据
            console.log('《《《WebSocket send data success! ' +
                DateUtils.getNowTime() + '》》》%c CMD=' +
                decodeWsMsg.cmd + '(' + Command2EnStr[decodeWsMsg.cmd] + ')\n',
                'color:#000;font-size:24px', decodeWsMsg);
        }
    }
}
